/*
        Author: Nathan Newman
        File Name: pcbCode.c
        Description: Main source file for module R2 in which process control blocks are defined
        Last updated: 9/17/11

*/

#include "mpx_supt.h"
#include "commhand.h"
#include "pcbCode.h"
#include "r3code.h"

//Two queues: readyQ and blockedQ, each consisting of suspended and non-suspended pcbs
//readyQ in priority order
//blockedQ is Last-in, First-out; i.e., new pcbs inserted at end
q readyQ = { PRIORITY, 0, 0, 0 }, blockedQ = { FCFS, 0, 0, 0 }; 




//|-------------PERMANENT commands-----------------|\\
/* allocate memory for a new pcb and its stack */
pcb* alloc_pcb(){
	pcb* pcbPtr = sys_alloc_mem(sizeof(pcb));
	//stack
	pcbPtr->stackBase = sys_alloc_mem(STACK_SIZE*sizeof(unsigned char));
	pcbPtr->stackTop = pcbPtr->stackBase + STACK_SIZE - sizeof(context); //recall that as the stack fills up, the address of the stack top DECREASES!! (towards the stack base)
	// memory
	//for now, unused
	pcbPtr->memSize = 0;
	pcbPtr->loadAddr = 0;
	pcbPtr->execAddr = 0;
	//queue pointers initially point to 0
	pcbPtr->frontPtr = 0;
	pcbPtr->backPtr = 0;
	
	return pcbPtr;
}

/* free memory associated with pcb, including stack and all */
int free_pcb(pcb* willy){
	int err = 0; //err used for debugging
	if(willy->loadAddr && sys_free_mem(willy->loadAddr)) err-=1;
	if(willy->execAddr && sys_free_mem(willy->execAddr)) err-=2;
	if(sys_free_mem(willy->stackBase)) err-=4;
	if(sys_free_mem(willy->loadAddr)) err-=8;
	if(sys_free_mem(willy)) err-=16;
	//if(err) printf("The error is %d\n",err); //DEBUGGING ONLY
	return err;
}

/* Allocate memory and then initialize content for a pcb */
pcb* setup_pcb(char pname[MAX_ARGLEN],int p,enum pcbClass c){
	pcb *newPcb;
	if(find_pcb(pname)) return 0; //don't want naming redundancies
	newPcb = alloc_pcb();
	strcpy(newPcb->name,pname);
	newPcb->priority = p;
	newPcb->class = c;
	newPcb->isSuspended = FALSE;
	newPcb->state = READY;
	return newPcb;
}

/* find the pcb with the given name; VERY IMPORTANT */
pcb* find_pcb(char* pname){
	int i;
	pcb *checkPtr;
	q* allQs[] = {&readyQ,&blockedQ};
	
	//iterate through all queues, starting at the first element of each queue and
	//checking for name equality.  If no match, return 0
	for(i=0;i<2;i++){
		if( (*(allQs +i))->size < 1 ) continue;
		checkPtr = (*(allQs + i))->qFirstPtr;
		while(checkPtr){
			if(strcmp(checkPtr->name,pname)==0) return checkPtr;
			checkPtr = checkPtr->backPtr;
		}
	}
	return 0;
}

/* insert the pcb into the given q (queue structure) */
int insert_pcb(pcb *toInsertPtr,q *qPtr){
	int p,num;
	pcb* lastPcbPtr=0; 
	pcb* frontPcbPtr=0;
	pcb* backPcbPtr=0;
	
	//check that null isn't passed in
	if(!toInsertPtr || !qPtr) return -1;
	
	//easiest case: queue is empty.  Don't care about ordering
	if(qPtr->size < 1) {
		toInsertPtr->frontPtr = 0;
		toInsertPtr->backPtr = 0;
		qPtr->qFirstPtr = toInsertPtr;
		qPtr->qLastPtr = toInsertPtr;
		qPtr->size = 1;
		return 0;
	}
	switch (qPtr->ordering) {
		case FCFS:
			//add the pcb to the end of the queue
			lastPcbPtr = qPtr->qLastPtr;
			lastPcbPtr->backPtr = toInsertPtr;
			toInsertPtr->frontPtr = lastPcbPtr;
			toInsertPtr->backPtr = 0;
			qPtr->qLastPtr = toInsertPtr;
			qPtr->size++;
			break;
		
		case PRIORITY:
			frontPcbPtr = qPtr->qFirstPtr; 
			backPcbPtr = qPtr->qLastPtr;
			p = toInsertPtr->priority;
			if(p <= backPcbPtr->priority){
				//target pcb is lower in priority than everything
				backPcbPtr->backPtr = toInsertPtr;
				toInsertPtr->frontPtr = backPcbPtr;
				toInsertPtr->backPtr = 0;
				qPtr->qLastPtr = toInsertPtr;
				qPtr->size++;
				break;
			}
			if(p > frontPcbPtr->priority){
				//target pcb is higher in priority than everything
				frontPcbPtr->frontPtr = toInsertPtr;
				toInsertPtr->backPtr = frontPcbPtr;
				toInsertPtr->frontPtr = 0;
				qPtr->qFirstPtr = toInsertPtr;
				qPtr->size++;
				break;
			}
			
			//else find the insertion position
			backPcbPtr = qPtr->qFirstPtr;
			while(p <= backPcbPtr->priority){ 
				//iterate until backPcbPtr points to the pcb directly below the one to be inserted (priority-wise) 
				backPcbPtr = backPcbPtr->backPtr;
				
				//The node should be placed internally; if not, something went wrong
				if(!backPcbPtr) return -1;
			}
			frontPcbPtr = backPcbPtr->frontPtr;
			
			//update pcbs' pointers
			toInsertPtr->frontPtr = frontPcbPtr;
			toInsertPtr->backPtr = backPcbPtr;
			backPcbPtr->frontPtr = toInsertPtr;
			frontPcbPtr->backPtr = toInsertPtr;
			
			qPtr->size++;
			break;
		
		default:
				return -1;
	}
	return 0;
}

/* removes pcb from its current queue */
int remove_pcb(pcb *toRemovePtr){
	q *holdQPtr;
	
	//determine the pcb's current queue
	switch (toRemovePtr->state) {
		case BLOCKED:
			holdQPtr = &blockedQ;	
			break;
		
		case READY:
			holdQPtr = &readyQ;
			break;
		
		default:
			return ERR_STATE;
	}
	
	if(holdQPtr->size < 1) return ERR_STATE;
	
	//Consider all cases-- removal from head, tail, or internal
	
	if(strcmp(toRemovePtr->name,holdQPtr->qFirstPtr->name)==0){
		//remove head
		holdQPtr->qFirstPtr = toRemovePtr->backPtr; //hold q points to next element as first (if it exists)
		if(toRemovePtr->backPtr) toRemovePtr->backPtr->frontPtr = 0; //if q isn't empty, make the head's front point to 0
		else holdQPtr->qLastPtr = 0; //q is empty if the removed has no back		
	}
	else if(strcmp(toRemovePtr->name,holdQPtr->qLastPtr->name)==0) {
		//remove tail in the case where the tail != the head (tail == head == toRemove is covered in previous condition
		holdQPtr->qLastPtr = toRemovePtr->frontPtr;
		toRemovePtr->frontPtr->backPtr = 0;
	}
	else {
		//the pcb to remove is neither the head nor the tail, so don't have to update the first or last q element
		toRemovePtr->frontPtr->backPtr = toRemovePtr->backPtr;
		toRemovePtr->backPtr->frontPtr = toRemovePtr->frontPtr;
	}
	
	//0 out the removed pcb's succ/pred for good measure, and update q size
	toRemovePtr->frontPtr = 0;
	toRemovePtr->backPtr = 0;
	holdQPtr->size--;
	
	return 0;
}

q* getQ(enum pcbClass pClass) {
	switch (pClass){
		case READY: return &readyQ;
		case BLOCKED: return &blockedQ;
		default: return 0;
	}
}
